# Практика код ## Вычисление суммы без использования буфера Пример, где подсчитывается сумма по записям коллекции Oil_PlanFactoryShipPos: ```scala //получаем все записи коллекции по родителю (объект rop) Oil_PlanFactoryShipPosApi().byParent(rop) //получаем только значение поля nQtyLoad //в случае незаполненности поля необходимо иметь значение 0, иначе в дальнейшем сумма с null = null .map(_.get(_.nQtyLoad).nvl(0.nn)) //сложение, результат которого будет обернут безопасной конструкцией Option .reduceOption(_ + _) //распаковка Option //если внутри был null или в коллекции не было записей, то вернётся указанное значение 0.nn .getOrElse(0.nn) ``` ## Группировка объектов с использованием null-типов При группировке объектов коллекций, в которых используется null-типы нужно учитывать, что метод `groupBy` считает хэши группируемых объектов, поэтому перед группировкой, нужно убедиться, что параметр, используемый для группировки, не равен null. Пример, с NPE: ```scala List( asd(123.nl, "asd".ns), asd(None.nl, "asd".ns), // У NLong с underlying = null хэш не посчитается ).groupBy(_.id).foreach { case (_, name) => println(name) } ``` Перед использованием `groupBy` необходимо отфильтровать значения или использовать метод `nvl`. ## Сравнение диапазона дат Допустим документы имеют поля dBeginDoc и dEndDoc. Фильтр имеет 2 поля dBeginFilter и dEndFilter. Чтобы найти все документы, которые началом или окончанием входят в диапазон, указанный в фильтре, нужно использовать следующее условие: ```scala dBeginDoc <= dEndFilter && dEndDoc >= dBeginFilter ``` ## .distinct или .toSet для scala-коллекций и особенности применения Если необходимо в scala-коллекции держать только уникальные объекты, можно использовать методы: - `.distinct` - `.toSet` Перед использованием метода `.distinct` scala-коллекцию необходимо подготовить: убрать значения null. Иначе в ходе выполнения программы выпадет ошибка `java.lang.NullPointerException`. Пример использования и демонстрация поведения методов: ```scala test("distinctOrToSet") { val data: Seq[NString] = Seq("h".ns, "i".ns, "i".ns, None.ns, None.ns) println("Результат работы .distinct с null внутри scala-коллекции:") try { println(data.distinct) } catch { case e: Throwable => println("Ошибка:\n" + "java.lang.NullPointerException") } finally { println("\nРезультат работы .filter(_.isNotNull).distinct:") println(data.filter(_.isNotNull).distinct) try { println("\nРезультат работы .toSet с null внутри scala-коллекции:") println(data.toSet) } catch { case e: Throwable => println("Ошибка:\n" + "java.lang.NullPointerException\n") } } } ``` Результат: ```{note} Результат работы .distinct с null внутри scala-коллекции: Ошибка: java.lang.NullPointerException Результат работы .filter(_.isNotNull).distinct: List(h, i) Результат работы .toSet с null внутри scala-коллекции: Set(h, i, Null) ``` ## `immutable.Map.builder` вместо `mutable.Map` Если по результату сформированной Map она больше не изменяется, то для формирования лучше использовать конструктор `immutable.Map.builder`, чем `mutable.Map`. Пример: ```scala val map = Map.newBuilder[NString, NString] map ++= Map("1" -> "11") map += "2" -> "22" map.result() //Map("1" -> "11", "2" -> "22") ``` ## Признак наличия модуля на проекте ```scala val isInstallProModule = session.sbtClassLoader.getModuleMap.containsKey("pro") ``` Вернёт true, если такой модуль есть в проекте, иначе false. ## Когда использовать ASQL/ ASelect/ OQuery/ TxIndex/ refreshByParent и byParent у коллекций Про данные инструменты можно почитать [здесь](../../020_common/055_взаимодействие_с_базой.md#взаимодействие-с-базой-данных). ### ASQL Выполнение реляционного запроса на чтение. ```{attention} Вызывает транзакцию в БД, не учитывая значения в кэше. ``` Удобен для получения значения по одному столбцу результата запроса. ```scala val idaWagonByTrain = ASQL""" select string_agg(cast(rwt.idWagon as varchar), ', ') from Rzd_TrainWagon rwt where rwt.idTrain = $idpTask """ //берется 1ый столбец результата запроса, как NString //если результат запроса вернул несколько строк, то будет взята первая в конструкции Option, // но предполагается, что результат вернёт максимум 1 строку .as(nStr(1).singleOpt) //если Option пустой, т.е. в результате запроса не было строк, то вернётся None.ns .getOrElse(None.ns) ``` `$idpTask` это подстановка значения из переменной scala `idpTask` в запрос связанной переменной (binding). ```{attention} Нельзя использовать внутри цикла. Это приведёт к многочилсенным транзакциям в БД. ``` Вместо использования `ASQL` внутри цикла с множественными транзакциями в БД нужно перед циклом одной транзакцией сформировать массив данных, который дальше будет использоваться внутри цикла. ### ATSQL Выполнение реляционного запроса с изменением данных или блокировками. Используется редко, в основном в ядровых процедурах, потому что минует серверную логику, записи в системные миксины, ведение аудитов и другое. ### ASelect Выполнение реляционного запроса на чтение/запись. ```{attention} Вызывает транзакцию в БД, не учитывая значения в кэше. ``` Используется в основном для чтения, потому что при изменении данных минует серверную логику, записи в системные миксины, ведение аудитов и другое. Удобен для получения значений по нескольким столбцам результата запроса. ```scala val mapPerson: Map[NLong, NString] = new ASelect { val sCode = asNString("sCode") val id = asNLong("id") SQL""" select p.id ,p.sCode from Bs_Person p where idObjectType = $idvObjectType """ }.map { rv => //rv представляет собой одну строку результата запроса rv.id() -> rv.sCode() }.toMap ``` `$idvObjectType` это подстановка значения из переменной scala `idvObjectType` в запрос связанной переменной (binding). ```{attention} Нельзя использовать внутри цикла. Это приведёт к многочилсенным транзакциям в БД. ``` Вместо использования `ASelect` внутри цикла с множественными транзакциями в БД нужно перед циклом одной транзакцией сформировать массив данных, который дальше будет использоваться внутри цикла. ### Когда использовать ASQL, а когда ASelect ASQL удобен для получения значения по одному столбцу результата запроса. ASelect удобен для получения значений по нескольким столбцам результата запроса. ### Подстановка связанных переменных (binding) Актуально для инструментов ASQL, ATSQL, ASelect. ```{note} Использование ASQL с подстановкой связанных переменных является полезной практикой, потому что запрос остаётся неизменным, меняется лишь значение параметра. Тем самым запрос не воспринимается системой, как новый, и будет записан в системную таблицу запросов единожды, что не приводит к распуханию БД. ``` Инструмент `ASQL"""<текст запроса>"""` подставляет бинды с учётом типа данных переменной. - Если бы была подстановка `NString`, то `ASQL` сам обернул бы значение в одинарные кавычки, т.е. нет необходимости их указывать вручную. - Если `NString` подставляется в текст `s"""<текст запроса>"""`, то одинарные кавычки необходимо указывать вручную. Если текст запроса для `ASQL"""<текст запроса>"""` собирается динамически средствами scala, в том числе название таблицы для select формируется переменной, то его необходимо подставлять через ```#$bind```, чтобы ASQL не обернул подставляемое значение в одинарные кавычки. ```scala val sNameTb = { if (a = 1) "Bs_Goods".ns else "Bs_Person".ns } val ida: List[NLong] = ASQL""" select t.id from #$sNameTb where idObjectType = $idvObjectType """.as(nLong(1).*) ```